#include "cmd_buffer.h"

/**
 * @brief 	 加锁
 */
#define CB_LOCK(p_cb)       \
    do {                    \
        if ((p_cb)->lock)   \
            (p_cb)->lock(); \
    } while (0)

/**
 * @brief 	 解锁
 */
#define CB_UNLOCK(p_cb)       \
    do {                      \
        if ((p_cb)->unlock)   \
            (p_cb)->unlock(); \
    } while (0)

/**
 * @fn CB_init
 * @brief 初始化指令处理对象
 * @param cb [in] 指令处理对象指针
 * @param info [in] 指令数据信息环形缓冲区对象指针
 * @param low [in] 指令数据环形缓冲区对象指针
 * @param lock [in] 加锁操作，用于数据存取的互斥操作
 * @param unlock [in] 解锁操作，用于数据存取的互斥操作
 * @return CmdBufferErrType 错误类型
 */
CmdBufferErrType CB_init(CmdBuffer *cb, RingBuffer *info, RingBuffer *low, void (*lock)(void), void (*unlock)(void))
{
    // 参数合法性判断
    if ((!cb) || (!info) || (!low)) {
        return CB_PARAM_ERR;
    }

    if ((lock || unlock) && (!(lock && unlock))) {
        return CB_PARAM_ERR;
    }

    // 指令处理结构体元素赋值
    cb->info   = info;
    cb->low    = low;
    cb->lock   = lock;
    cb->unlock = unlock;

    return CB_OK;
}

/**
 * @fn CB_push
 * @brief 将指令数据存放缓冲区
 * @param cb [in] 指令处理对象指针
 * @param cmd_str [in] 传入的指令
 * @param cmd_size [in] 传入的指令长度
 * @return CmdBufferErrType 错误类型
 */
CmdBufferErrType CB_push(CmdBuffer *cb, const uint8_t *cmd_str, const uint32_t cmd_size)
{
    CmdInfo info          = {0};
    RingBufferErrType res = RB_OK;

    // 参数合法性判断
    if ((!cb) || (!cmd_str) || (!cmd_size)) {
        return CB_PARAM_ERR;
    }

    // 存入指令之前加锁
    CB_LOCK(cb);

    // 先存入到底层缓冲区
    res = RB_push(cb->low, cmd_str, cmd_size, &info.start, &info.end);
    if (res != RB_OK) {
        CB_UNLOCK(cb);
        return CB_LOW_PUSH_ERR;
    }

    // 再存放数据信息
    res = RB_push(cb->info, &info, 1, 0, 0);
    if (res != RB_OK) {
        CB_UNLOCK(cb);
        return CB_INFO_PUSH_ERR;
    }

    // 存入指令之后解锁
    CB_UNLOCK(cb);
    return CB_OK;
}

/**
 * @fn CB_push_dma
 * @brief 使用DMA将指令数据存放缓冲区
 * @param cb [in] 指令处理对象指针
 * @param pos [in] DMA存入数据后，数据尾索引
 * @return CmdBufferErrType 错误类型
 */
CmdBufferErrType CB_push_dma(CmdBuffer *cb, const uint32_t pos)
{
    CmdInfo info          = {0};
    RingBufferErrType res = RB_OK;

    // 参数合法性判断
    if (!cb) {
        return CB_PARAM_ERR;
    }

    // 存入指令之前加锁
    CB_LOCK(cb);

    // 先存入到底层缓冲区，使用DMA
    res = RB_push_dma(cb->low, pos, &info.start, &info.end);
    if (res != RB_OK) {
        CB_UNLOCK(cb);
        return CB_LOW_PUSH_ERR;
    }

    // 再存放数据信息
    res = RB_push(cb->info, &info, 1, 0, 0);
    if (res != RB_OK) {
        CB_UNLOCK(cb);
        return CB_INFO_PUSH_ERR;
    }

    // 存入指令之后解锁
    CB_UNLOCK(cb);

    return CB_OK;
}

/**
 * @fn CB_get_pop_cmd_size
 * @brief 获取可读取指令的数量
 * @param cb [in] 指令处理对象指针
 * @return CmdBufferErrType 错误类型
 */
uint32_t CB_get_pop_cmd_size(CmdBuffer *cb)
{
    CmdInfo info          = {0};
    RingBufferErrType res = RB_OK;

    // 参数合法性判断
    if (!cb) {
        return 0;
    }

    // 查看数据信息
    res = RB_look(cb->info, 1, &info);
    if (res != RB_OK) {
        return 0;
    }

    // 对分成连段存储的数据的数量进行处理
    if (info.start < info.end) {
        return info.end - info.start;
    } else {
        return cb->low->elem_max - info.start + info.end;
    }
}

/**
 * @fn CB_pop
 * @brief 获取可读取指令的数量
 * @param cb [in] 指令处理对象指针
 * @param cmd_size [in] 需要读取的指令的长度
 * @param cmd_str [out] 读取指令存放的缓冲区，必须提供有效的指针
 * @return CmdBufferErrType 错误类型
 */
CmdBufferErrType CB_pop(CmdBuffer *cb, const uint32_t cmd_size, uint8_t *cmd_str)
{
    CmdInfo info          = {0};
    uint32_t size         = 0;
    RingBufferErrType res = RB_OK;

    // 参数合法性判断
    if ((!cb) || (!cmd_size) || (!cmd_str)) {
        return CB_PARAM_ERR;
    }

    // 取数据前加锁
    CB_LOCK(cb);

    // 获取数据信息
    res = RB_pop(cb->info, 1, &info);
    if (res != RB_OK) {
        CB_UNLOCK(cb);
        return CB_INFO_POP_ERR;
    }

    // 计算数据长度
    if (info.start < info.end) {
        size = info.end - info.start;
    } else {
        size = cb->low->elem_max - info.start + info.end;
    }

    // 判断与取出长度是否一致
    if (size != cmd_size) {
        // 错误退出时解锁
        CB_UNLOCK(cb);
        return CB_PARAM_ERR;
    }

    // 从底层取出数据
    res = RB_pop(cb->low, cmd_size, cmd_str);
    if (res != RB_OK) {
        // 错误退出时解锁
        CB_UNLOCK(cb);
        return CB_LOW_POP_ERR;
    }

    // 取数据后解锁
    CB_UNLOCK(cb);

    return CB_OK;
}
